#include "BadSqlQueryException.hpp"

#define A(str) encode<EncodingT,ansi>(str)
#define C(str) encode<ansi,EncodingT>(str)

NAMESPACE_BEGIN(data_access)

template <class EncodingT>
MYSQL * _DataConnection<EncodingT>::connection_handle = NULL;

template <class EncodingT>
_DataConnection<EncodingT> * _DataConnection<EncodingT>::m_instance = NULL;

// constructor
template <class EncodingT>
_DataConnection<EncodingT>::_DataConnection()
{
    m_logger = &Category::getInstance(LOGNAME);
    m_logger->debugStream() << "creation DataConnection";
    transaction_inProgress = false;
    //initialization code
}

template <class EncodingT>
bool _DataConnection<EncodingT>::openConnection(typename EncodingT::string_t const& host,
                                                int port,
                                                typename EncodingT::string_t const& db,
                                                typename EncodingT::string_t const& user,
                                                typename EncodingT::string_t const& passwd)
{
    MYSQL * error = NULL;
    //open connection
    //initialization connection
    if ((connection_handle = mysql_init(NULL))==NULL){
        return false;
    }
     if ((error=mysql_real_connect(connection_handle,A(host).c_str(),A(user).c_str(),A(passwd).c_str(),A(db).c_str(),port,0,0))==NULL){                                
        mysql_close(connection_handle);
        return false;
    }
    return true;
}

template <class EncodingT>
void _DataConnection<EncodingT>::closeConnection()
{
    //close connection
    mysql_close(connection_handle);
    //m_logger->debugStream() << "close connection";
}

template <class EncodingT>
_DataConnection<EncodingT> * _DataConnection<EncodingT>::getInstance()
{
    //check connection and create DataConnection
    if (connection_handle) {
        if (!m_instance)
            m_instance = new _DataConnection<EncodingT>();
        return m_instance;
    }
    else return NULL;
}

template <class EncodingT>
typename EncodingT::string_t _DataConnection<EncodingT>::formatSelect(vector<typename EncodingT::string_t> const& columns,
                                                    vector<typename EncodingT::string_t> const& tables,
                                                    typename EncodingT::string_t const& filter, bool forUpdate, bool noWait) const
{
    //Create SQL query
    typename EncodingT::string_t query = C("SELECT ");
    if (columns.size() == 0) {
        m_logger->fatalStream() << "bad query : no columns defined in SELECT query";
        throw BadSqlQueryException("no columns defined in SELECT query");
    }
    else query += columns[0];
    for (size_t i=1; i<columns.size(); i++) query += C(", ") + columns[i];
    query += C(" FROM ");
    if (tables.size() == 0) {
        m_logger->fatalStream() << "bad query : no tables defined in SELECT query";
        throw BadSqlQueryException("no tables defined in SELECT query");
    }
    else query += tables[0];
    for (size_t i=1; i<tables.size(); i++) query += C(", ") + tables[i];
    if (filter != EncodingT::EMPTY) {
        query += C(" WHERE ") + filter;
    }
    if (forUpdate) query += C(" FOR UPDATE ");
    if (noWait) query += C(" NOWAIT ");
    query += C(";");
    m_logger->debugStream() << "query : " << A(query);
    return query;
}

template <class EncodingT>
typename _DataConnection<EncodingT>::resultset
_DataConnection<EncodingT>::select(vector<typename EncodingT::string_t> const& columns,
                                   vector<typename EncodingT::string_t> const& tables,
                                   typename EncodingT::string_t const& filter) const
{
    MYSQL_RES *res;
    MYSQL_ROW row;
    resultset result;
    typename EncodingT::string_t query = formatSelect(columns, tables, filter, false, false);
    int err=mysql_real_query(connection_handle,A(query).c_str(),query.length());
    if (err){
        m_logger->fatalStream() << "bad query : " << mysql_error(connection_handle);
        throw BadSqlQueryException(mysql_error(connection_handle));
    }
    res=mysql_use_result(connection_handle);
    size_t num_fields = mysql_num_fields(res); 
    while (row=mysql_fetch_row(res)) {
        vector<typename EncodingT::string_t> one_row;
        for (size_t i=0; i<num_fields; ++i) {
            const char * str = (const char*)row[i];
            one_row.push_back(C(str?str:""));
        }
        result.push_back(one_row);
    }
    mysql_free_result(res);
    return result;
}

template <class EncodingT>
typename _DataConnection<EncodingT>::resultset
_DataConnection<EncodingT>::selectForUpdate(vector<typename EncodingT::string_t> const& columns,
                                            vector<typename EncodingT::string_t> const& tables,
                                            typename EncodingT::string_t const& filter, bool nowait)
{
    MYSQL_RES *res;
    MYSQL_ROW row;
    resultset result;
    typename EncodingT::string_t query = formatSelect(columns, tables, filter, true, nowait);
    int err=mysql_real_query(connection_handle,A(query).c_str(),query.length());
    if (err){
        m_logger->fatalStream() << "bad query : " << mysql_error(connection_handle);
        throw BadSqlQueryException(mysql_error(connection_handle));
    }
    res=mysql_use_result(connection_handle);
    size_t num_fields = mysql_num_fields(res); 
    while (row=mysql_fetch_row(res)) {
        vector<typename EncodingT::string_t> one_row;
        for (size_t i=0; i<num_fields; ++i) {
            const char * str = (const char*)row[i];
            one_row.push_back(C(str?str:""));
        }
        result.push_back(one_row);
    }
    mysql_free_result(res);
    return result;
}

template <class EncodingT>
typename EncodingT::string_t _DataConnection<EncodingT>::formatInsert(typename EncodingT::string_t const& table,
                                        vector<typename EncodingT::string_t> const& columns,
                                        vector<typename EncodingT::string_t> const& values) const
{
    //Create SQL query
    typename EncodingT::string_t query = C("INSERT INTO ");
    if (table == EncodingT::EMPTY) {
        m_logger->fatalStream() << "bad query : no table defined in INSERT query";
        throw BadSqlQueryException("no table defined in INSERT query");
    }
    else query += table;
    query += C(" ( ");
    if (columns.size() == 0) {
        m_logger->fatalStream() << "bad query : no columns defined in INSERT query";
        throw BadSqlQueryException("no columns defined in INSERT query");
    }
    else query += columns[0];
    for (size_t i=1; i<columns.size(); i++) query += C(", ") + columns[i];
    query += C(" ) VALUES ( ");
    if (values.size() == 0) {
        m_logger->fatalStream() << "bad query : no values defined in INSERT query";
        throw BadSqlQueryException("no values defined in INSERT query");
    }
    else query += values[0];
    for (size_t i=1; i<values.size(); i++) query += C(", ") + values[i];
    query += C(" );");
    m_logger->debugStream() << "query : " << A(query);
    return query;
}

template <class EncodingT>
bool _DataConnection<EncodingT>::insert(typename EncodingT::string_t const& table,
                                        vector<typename EncodingT::string_t> const& columns,
                                        vector<typename EncodingT::string_t> const& values)
{
    typename EncodingT::string_t query = formatInsert(table, columns, values);
    int err=mysql_real_query(connection_handle,A(query).c_str(),query.length());
    if (err){
        m_logger->fatalStream() << "bad query : " << mysql_error(connection_handle);
        throw BadSqlQueryException(mysql_error(connection_handle));
    }
    return true;
}

template <class EncodingT>
typename EncodingT::string_t _DataConnection<EncodingT>::formatDeleteFrom(typename EncodingT::string_t const& table,
                                        typename EncodingT::string_t const& filter) const
{
    //Create SQL query
    typename EncodingT::string_t query = C("DELETE FROM ");
    if (table == EncodingT::EMPTY) {
        m_logger->fatalStream() << "bad query : no table defined in DELETE query";
        throw BadSqlQueryException("no table defined in DELETE query");
    }
    else query += table;
    if (filter != EncodingT::EMPTY) {
        query += C(" WHERE ") + filter;
    }
    query += C(";");
    m_logger->debugStream() << "query : " << A(query);
    return query;
}

template <class EncodingT>
bool _DataConnection<EncodingT>::deleteFrom(typename EncodingT::string_t const& table,
                                        typename EncodingT::string_t const& filter)
{
    typename EncodingT::string_t query = formatDeleteFrom(table, filter);
    int err=mysql_real_query(connection_handle,A(query).c_str(),query.length());
    if (err){
        m_logger->fatalStream() << "bad query : " << mysql_error(connection_handle);
        throw BadSqlQueryException(mysql_error(connection_handle));
    }
    return true;
}

template <class EncodingT>
typename EncodingT::string_t _DataConnection<EncodingT>::formatUpdate(typename EncodingT::string_t const& table,
                                        vector<typename EncodingT::string_t> const& columns,
                                        vector<typename EncodingT::string_t> const& values,
                                        typename EncodingT::string_t const& filter) const
{
    //Create SQL query
    typename EncodingT::string_t query = C("UPDATE ");
    if (table == EncodingT::EMPTY) {
        m_logger->fatalStream() << "bad query : no table defined in UPDATE query";
        throw BadSqlQueryException("no table defined in UPDATE query");
    }
    else query += table;
    query += C(" SET ");
    if (columns.size() == 0 || values.size() == 0) {
        m_logger->fatalStream() << "bad query : no columns defined in UPDATE query";
        throw BadSqlQueryException("no columns defined in UPDATE query");
    }
    if (columns.size() != values.size()) {
        m_logger->fatalStream() << "bad query : invalid columns and values sizes in UPDATE query";
        throw BadSqlQueryException("invalid columns and values sizes in UPDATE query");
    }
    else query += columns[0] + C("=") + values[0];
    for (size_t i=1; i<columns.size(); i++) query += C(", ") + columns[i] + C("=") + values[i];
    if (filter != EncodingT::EMPTY) {
        query += C(" WHERE ") + filter;
    }
    query += C(";");
    m_logger->debugStream() << "query : " << A(query);
    return query;
}

template <class EncodingT>
bool _DataConnection<EncodingT>::update(typename EncodingT::string_t const& table,
                                        vector<typename EncodingT::string_t> const& columns,
                                        vector<typename EncodingT::string_t> const& values,
                                        typename EncodingT::string_t const& filter)
{
    typename EncodingT::string_t query = formatUpdate(table, columns, values, filter);
    int err=mysql_real_query(connection_handle,A(query).c_str(),query.length());
    if (err){
        m_logger->fatalStream() << "bad query : " << mysql_error(connection_handle);
        throw BadSqlQueryException(mysql_error(connection_handle));
    }
    return true;
}

template <class EncodingT>
int _DataConnection<EncodingT>::getLastInsertID() const
{
    int result = 0;
    MYSQL_RES *res;
    MYSQL_ROW row;
    string query = "SELECT LAST_INSERT_ID();";
    m_logger->debugStream() << "query : " << query;
    int err=mysql_real_query(connection_handle, query.c_str(),query.length());
    if (err){
        m_logger->fatalStream() << "bad query : " << mysql_error(connection_handle);
        throw BadSqlQueryException(mysql_error(connection_handle));
    }
    res=mysql_use_result(connection_handle);
    size_t num_fields = mysql_num_fields(res); 
    if (row=mysql_fetch_row(res)) {
        if (num_fields && row[0]) result = atoi(row[0]);
    }
    mysql_free_result(res);
    return result;
}

template <class EncodingT>
int _DataConnection<EncodingT>::selectMaxID(typename EncodingT::string_t const& id,
                                            typename EncodingT::string_t const& table) const
{
    int result = 0;
    MYSQL_RES *res;
    MYSQL_ROW row;
    string query = string("SELECT max(") + A(id) + ") FROM "+ A(table) + " FOR UPDATE;";
    m_logger->debugStream() << "query : " << query;
    int err=mysql_real_query(connection_handle, query.c_str(),query.length());
    if (err){
        m_logger->fatalStream() << "bad query : " << mysql_error(connection_handle);
        throw BadSqlQueryException(mysql_error(connection_handle));
    }
    res=mysql_use_result(connection_handle);
    size_t num_fields = mysql_num_fields(res); 
    if (row=mysql_fetch_row(res)) {
        if (num_fields && row[0]) result = atoi(row[0]);
    }
    mysql_free_result(res);
    return result;
}

template <class EncodingT>
bool _DataConnection<EncodingT>::commit()
{
    static char * commit = "COMMIT;";
    string query = commit;
    m_logger->debugStream() << "query : " << query;
    int err=mysql_real_query(connection_handle,query.c_str(),query.length());
    if (err){
        m_logger->fatalStream() << "bad query : " << mysql_error(connection_handle);
        throw BadSqlQueryException(mysql_error(connection_handle));
    }
    transaction_inProgress = false;
    return true;
}

template <class EncodingT>
bool _DataConnection<EncodingT>::rollback()
{
    static char * rollback = "ROLLBACK;";
    string query = rollback;
    m_logger->debugStream() << "query : " << query;
    int err=mysql_real_query(connection_handle,query.c_str(),query.length());
    if (err){
        m_logger->fatalStream() << "bad query : " << mysql_error(connection_handle);
        throw BadSqlQueryException(mysql_error(connection_handle));
    }
    transaction_inProgress = false;
    return true;
}

template <class EncodingT>
bool _DataConnection<EncodingT>::startTransaction()
{
    static char * start_transaction = "START TRANSACTION;";
    string query = start_transaction;
    m_logger->debugStream() << "query : " << query;
    int err=mysql_real_query(connection_handle,query.c_str(),query.length());
    if (err){
        m_logger->fatalStream() << "bad query : " << mysql_error(connection_handle);
        throw BadSqlQueryException(mysql_error(connection_handle));
    }
    transaction_inProgress = true;
    return true;
}

template <class EncodingT>
bool _DataConnection<EncodingT>::isTransactionInProgress()
{
    return transaction_inProgress;
}

NAMESPACE_END

#undef C
#undef A

